package com.example.demo.common.file.service;


import com.example.demo.common.exception.EbsException;
import com.example.demo.common.persistence.model.SysDocFile;
import com.example.demo.common.vo.EbsFile;
import com.example.demo.core.cache.CacheManager;
import com.example.demo.core.util.EntityUtil;
import com.example.demo.core.util.ExecTime;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


/**
 * 功能描述：ftp文件存储实现。
 */
@Service
@Transactional(propagation= Propagation.NOT_SUPPORTED)
public class FtpFileService extends AbstractFileService implements FileService {
	
	private static Logger logger = LoggerFactory.getLogger(FtpFileService.class);
	
	private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
	
	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(String local, String module1, String module2) {
		
		File file = new File(local);
		if(file.exists()) {
			return putFile(file,module1,module2);
		}
		return "";
	}

	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(File local, String module1, String module2) {

		String fileName = local.getName();
		
		String fileId = putFile(local,module1,module2,fileName);
		return fileId;
	}

	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(MultipartFile local, String module1, String module2) {
		 
		try {
			String checkSum = createChecksum(local.getInputStream());
			return putFile(local.getInputStream(),module1,module2,local.getOriginalFilename(),checkSum);
		} catch (IOException e) { 
			throw new EbsException(e);
		}
	}	
	
	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(String local, String module1, String module2, String rename) {
		
		File file = new File(local);
		if(file.exists()) {
			return putFile(file,module1,module2,rename);
		}
		return "";
	}

	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(File local, String module1, String module2, String rename) {

		if(!local.exists()) return null;
		
		try {
			InputStream input = new FileInputStream(local);
			String checkSum = createChecksum(new FileInputStream(local));
			String fileId = putFile(input,module1,module2,rename,checkSum);
			return fileId;
		} catch(Exception e) {
			throw new EbsException(e);
		}
	}
	
	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(InputStream local, String module1, String module2,String rename) {
		
		return putFile(local, module1, module2,rename,"");
	}
	
	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(MultipartFile local, String module1, String module2, String rename) {
		try {
			String checkSum = createChecksum(local.getInputStream());
			return putFile(local.getInputStream(),module1,module2,rename,checkSum);
		} catch (IOException e) { 
			throw new EbsException(e);
		}
	}

	@Transactional(propagation= Propagation.NOT_SUPPORTED)
	public String putFile(InputStream local, String module1, String module2,String rename,String checkSum) {

		try {
			/** 文件排重 */
			if(StringUtils.isNotEmpty(checkSum)) {
				SysDocFile sysDocFileOld = sysDocFileMapper.selectByChecksum(checkSum);
				if(sysDocFileOld != null && StringUtils.isNotEmpty(sysDocFileOld.getId())) {
					
					if(!StringUtils.endsWithIgnoreCase(sysDocFileOld.getFileName(), rename) ||
							!StringUtils.endsWithIgnoreCase(sysDocFileOld.getModule1(), module1) ||
							!StringUtils.endsWithIgnoreCase(sysDocFileOld.getModule2(), module2)) {
						sysDocFileOld.setId(getUUID());
						sysDocFileOld.setFileName(rename);
						sysDocFileOld.setModule1(module1);
						sysDocFileOld.setModule2(module2);
						sysDocFileMapper.insert(sysDocFileOld); 
					}
					return sysDocFileOld.getId();
				}
			}
			
			String fileName = rename;
			Integer fileSize = local.available();
			
			String remoteFilePath = separator;
			String companyCode = getCompanyCode();
			String dateStr = DateFormatUtils.format(new Date(), "yyyyMMdd"); 
			String fileId = getUUID();
			remoteFilePath += companyCode + separator;
			remoteFilePath += dateStr + separator;
			remoteFilePath += module1 + separator;
			remoteFilePath += module2 + separator;
			remoteFilePath += fileId + "." + FilenameUtils.getExtension(fileName);
			
			uploadFile(remoteFilePath,local); 			
			 
			SysDocFile sysDocFile = new SysDocFile();
			EntityUtil.setValue(sysDocFile);
			sysDocFile.setId(fileId);
			sysDocFile.setFileName(fileName);
			sysDocFile.setFilePath(remoteFilePath);
			sysDocFile.setFileSize(fileSize);
			sysDocFile.setFileType(FilenameUtils.getExtension(rename));
			sysDocFile.setModule1(module1);
			sysDocFile.setModule2(module2);
			sysDocFile.setServerNode(configServiceImpl.getString("ftp_server_node","a"));
			sysDocFile.setFileChecksum(checkSum);
			sysDocFile.setCreateTime(new Date());
			
			sysDocFileMapper.insert(sysDocFile); 
			return fileId;
		} catch(Exception e) {
			throw new EbsException(e);
		}		
	}	

	public EbsFile getFileById(String fileId) {

		SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
		
		return getEbsFile(sysDocFile);
	}
	
	public EbsFile getFileById(String fileId, String accessCode) {
		
		SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
		if(StringUtils.equals(sysDocFile.getAccessCode(), accessCode)) {
			return getEbsFile(sysDocFile);
		}
		throw new EbsException("提取码错误");
	}
	
	public EbsFile getFileById(String[] fileIds) {

		ZipOutputStream zos = null;
		InputStream input = null;
		try {
			String filePath = getTempFile("download", "zip","zip");
			String fileDir = FilenameUtils.getFullPath(filePath);
			mkdirs(fileDir);
			File file = new File(filePath);		
			
			zos = new ZipOutputStream(new FileOutputStream(file));  
	        ZipEntry ze = null;  
	        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];  
	        int readLen = 0;  
	        
			EbsFile ebsFile = null;
			String fileName = "打包文件.zip";
			boolean rename = false;//是否重命名的标识
			for(String fileId:fileIds) {
				ebsFile = getFileById(fileId);
				//根据当前文件组首个文件所属人获取所属人公司
				if(!rename && null != ebsFile){

						fileName = "file"+fileId+".zip";
						rename = true;
				}
				ze = new ZipEntry(ebsFile.getFileName()); 
				zos.putNextEntry(ze);  
				input = ebsFile.getInputStream();  
	            while ((readLen = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {  
	                zos.write(buffer, 0, readLen);  
	            }
	            IOUtils.closeQuietly(input);
			}
			EbsFile ebsZipFile = new EbsFile();
			ebsZipFile.setFile(file);
			ebsZipFile.setFileName(fileName);
			
			return ebsZipFile;
		} catch(Exception e) {
			throw new EbsException(e);
		} finally {
			IOUtils.closeQuietly(input);
			IOUtils.closeQuietly(zos);
		}
	}
	public EbsFile getGroupFileById(String[] fileIds) {
		ZipOutputStream zosAll = null;
		ZipOutputStream zos = null;
		ZipEntry zeAll = null;
		InputStream input = null;
		InputStream inputAll = null;
		try {
			String filePath = getTempFile("download", "zip","zip");
			String filePathAll = getTempFile("downloadAll", "zip","zip");
			String fileDir = FilenameUtils.getFullPath(filePath);
			String fileDirAll = FilenameUtils.getFullPath(filePathAll);
			mkdirs(fileDir);
			mkdirs(fileDirAll);
			File file = null;
			File fileAll = null;
			
	        ZipEntry ze = null;  
	        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];  
	        int readLenAll = 0;  
	        
			EbsFile ebsFile = null;
			String allFileName = "全部打包文件.zip";
			String fileName = "";
			boolean rename = false;//是否重命名的标识
			EbsFile ebsZipFile;
			
			fileAll = new File(filePathAll);
			zosAll = new ZipOutputStream(new FileOutputStream(fileAll)); 
			for(int i=0;i<fileIds.length;i++){
				String [] files = fileIds[i].split(",");
				fileName = "打包文件"+(i+1)+".zip";
				rename = false;//是否重命名的标识
				ebsZipFile = new EbsFile();
				file = new File(filePath);
				int readLen = 0; 
				int index = 0;
				zos = new ZipOutputStream(new FileOutputStream(file));
				for(String fileId:files) {
					ebsFile = getFileById(fileId);
					//根据当前文件组首个文件所属人获取所属人公司
					if(!rename && null != ebsFile){
						fileName = "file"+fileId+".zip";
						rename = true;
					}
					index += 1;
					ze = new ZipEntry("_"+index+ebsFile.getFileName());
					zos.putNextEntry(ze);  
					input = ebsFile.getInputStream();  
		            while ((readLen = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {  
		                zos.write(buffer, 0, readLen);  
		            }
		            IOUtils.closeQuietly(input);
				}
				IOUtils.closeQuietly(zos);
				
				ebsZipFile.setFile(file);
				ebsZipFile.setFileName(fileName);
				
				zeAll = new ZipEntry("_"+(i+1)+ebsZipFile.getFileName());
				zosAll.putNextEntry(zeAll);
				inputAll = ebsZipFile.getInputStream();  
	            while ((readLenAll = inputAll.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {  
	            	zosAll.write(buffer, 0, readLenAll);  
	            }
	            IOUtils.closeQuietly(inputAll);
			}
			EbsFile ebsZipFileAll = new EbsFile();
			ebsZipFileAll.setFile(fileAll);
			ebsZipFileAll.setFileName(allFileName);
			return ebsZipFileAll;
		} catch(Exception e) {
			throw new EbsException(e);
		} finally {
			IOUtils.closeQuietly(input);
			IOUtils.closeQuietly(zos);
			IOUtils.closeQuietly(inputAll);
			IOUtils.closeQuietly(zosAll);
		}
	}

	public EbsFile copyFileById(String fileId, OutputStream out) {

		SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
		return getEbsFile(sysDocFile,out,false);
	}

	public EbsFile getFileByPath(String filePath) {
		SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
		if(sysDocFile == null) {
			sysDocFile = new SysDocFile();
			sysDocFile.setFilePath(filePath);
			sysDocFile.setFileName(FilenameUtils.getName(filePath));
		}
		return getEbsFile(sysDocFile); 
	}		

	public EbsFile copyFileByPath(String filePath, OutputStream out) {

		SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
		return getEbsFile(sysDocFile,out,false);
	}
	
	public String getFileNameById(String fileId) {
		
		if(StringUtils.isEmpty(fileId)) return "";
		
		SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
		
		if(sysDocFile == null) return "";
		
		return sysDocFile.getFileName();
	}
	
	public String getFileNameByPath(String filePath) {
		
		SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
		return sysDocFile.getFileName();
	}
	
	public SysDocFile getSysDocFileById(String fileId) {
		SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
		return sysDocFile;
	}
	
	public List<SysDocFile> getSysDocFileByIds(String[] ids) {
		
		if(ids == null || ids.length == 0) return null;
		
		List<SysDocFile> list = sysDocFileMapper.selectBatchIds(Arrays.asList(ids));
		
		return list;
	}
	
	public SysDocFile getSysDocFileByPath(String filePath) {
		SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
		return sysDocFile;
		
	}
	
	private EbsFile getEbsFile(SysDocFile sysDocFile) {
		
		if(sysDocFile == null) return null;
		
		String filePath = getLocationDir();
		filePath += sysDocFile.getFilePath();
		
		String dir = FilenameUtils.getFullPath(filePath);
		mkdirs(dir);
		
		OutputStream out = null;
		try {
			File file = new File(filePath);
			boolean existsFile = sysDocFile.getId() == null?false:existsFile(sysDocFile,file);			
			if(!existsFile)
				out = new FileOutputStream(file);
			
			EbsFile ebsFile = getEbsFile(sysDocFile,out,existsFile);
			
			saveChecksum(sysDocFile,file);
			return ebsFile;
		} catch(Exception e) {
			throw new EbsException(e);
		} finally {
			IOUtils.closeQuietly(out);
		}
	}
	
	private EbsFile getEbsFile(SysDocFile sysDocFile,OutputStream out,boolean existsFile) {
		
		if(sysDocFile == null) return null;
		
		String filePath = getLocationDir();
		filePath += sysDocFile.getFilePath();

		try { 
			if(!existsFile) {
				downloadFile(sysDocFile.getFilePath(),out,sysDocFile.getServerNode()); 
				
				if(StringUtils.isNotEmpty(sysDocFile.getFileChecksum())) {
					String cacheKey = configServiceImpl.getNodeName() + sysDocFile.getFilePath();
					CacheManager.set(cacheKey, sysDocFile.getFileChecksum());
				}  
			}
			IOUtils.closeQuietly(out);
			
			EbsFile ebsFile = new EbsFile();
			File file = new File(filePath);
			ebsFile.setFile(file);
			ebsFile.setFileId(sysDocFile.getId());
			ebsFile.setFileName(sysDocFile.getFileName());
			ebsFile.setFilePath(sysDocFile.getFilePath());
			ebsFile.setOwnerUserId(sysDocFile.getOwnerUserId());
			return ebsFile;
		} catch(Exception e) {
			throw new EbsException(e);
		}
	}
	
	private boolean existsFile(SysDocFile sysDocFile,File file) {
		
		if(!file.exists()) return false;
		
		InputStream input = null;
		try {
			input = new FileInputStream(file);
			int fileSize = input.available();
			if(fileSize <= 0) { 
				return false;
			}
			
			String cacheKey = configServiceImpl.getNodeName() + sysDocFile.getFilePath();
			String checksum = CacheManager.get(cacheKey);
			if(StringUtils.isEmpty(checksum)) {
				checksum = createChecksum(input);
			}
			
			if(StringUtils.equalsIgnoreCase("bid2", sysDocFile.getFileType())) {
				if(StringUtils.equalsIgnoreCase(sysDocFile.getFileChecksum(), checksum) && fileSize >= sysDocFile.getFileSize()) {
					return true;
				}
			} else {
				if(StringUtils.equalsIgnoreCase(sysDocFile.getFileChecksum(), checksum) && fileSize == sysDocFile.getFileSize()) {
					return true;
				}
			}
			
			return false;
		} catch (Exception e) { 
			throw new EbsException(e);
		} finally {
			IOUtils.closeQuietly(input);
		}		
	}
	
	private void saveChecksum(SysDocFile sysDocFile,File file) {
	 
		if(StringUtils.isNotEmpty(sysDocFile.getFileChecksum()) || !file.exists() || sysDocFile.getId() == null) return;
		
		InputStream input = null;
		try {
			input = new FileInputStream(file);
			int fileSize = input.available();
			if(fileSize <= 0) { 
				return ;
			}
		 	
			String checksum = createChecksum(input);
			SysDocFile update = new SysDocFile();
			update.setId(sysDocFile.getId());
			update.setFileChecksum(checksum);
			update.setFileSize(fileSize);
			
			sysDocFileMapper.updateById(update);
		} catch (Exception e) { 
			throw new EbsException(e);
		} finally {
			IOUtils.closeQuietly(input);
		}		
	}

	private FTPClient getFtpClient() {
		
		try {
			FTPClient ftpClient = new FTPClient(); 
			ftpClient.setDefaultTimeout(10 * 1000);
			ftpClient.setConnectTimeout(10 * 1000);
			ftpClient.setDataTimeout(30 * 1000);
			ftpClient.setDefaultPort(NumberUtils.toInt(configServiceImpl.getString("ftp_port"),21));
			ftpClient.connect(configServiceImpl.getString("ftp_host"));
			ftpClient.login(configServiceImpl.getString("ftp_username"), configServiceImpl.getString("ftp_password")); 
			logger.info("FTP 已连接!");
			return ftpClient;
		} catch (Exception e) {
			throw new  EbsException(e);
		}
	}
	
	private FTPClient getFtpClient(String serverNode) {
		
		try {
			String ftpHost = configServiceImpl.getString("ftp_host_" + serverNode);
			String ftpUsername = configServiceImpl.getString("ftp_username_" + serverNode);
			String ftpPassword = configServiceImpl.getString("ftp_password_" + serverNode);
			String ftpPort = configServiceImpl.getString("ftp_port_" + serverNode);
			if(StringUtils.isEmpty(ftpHost)) {
				ftpHost = configServiceImpl.getString("ftp_host");
				ftpUsername = configServiceImpl.getString("ftp_username");
				ftpPassword = configServiceImpl.getString("ftp_password");
				ftpPort = configServiceImpl.getString("ftp_port");
			}
			
			FTPClient ftpClient =new FTPClient(); 
			ftpClient.setDefaultTimeout(10 * 1000);
			ftpClient.setConnectTimeout(10 * 1000);
			ftpClient.setDataTimeout(30 * 1000);
			ftpClient.setDefaultPort(NumberUtils.toInt(ftpPort,21));
			ftpClient.connect(ftpHost);
			ftpClient.login(ftpUsername, ftpPassword); 
			logger.info("FTP 已连接!");
			return ftpClient;
		} catch (Exception e) {
			throw new  EbsException(e);
		}
	}	
	
	private void disconnect(FTPClient ftpClient) {	
		
		try	{
			if(ftpClient != null) {
				ftpClient.logout();
				ftpClient.disconnect();
				ftpClient = null;
				logger.info("FTP 断开连接!");
			}
		} catch(Exception e) {
			logger.error("FTP 断开连接错误!",e);
		}
	}
	
	private boolean uploadFile(String remoteFilePath, InputStream local) {	
		
		logger.info("FTP上传：存储文件 = " + remoteFilePath);	
		
		while(remoteFilePath.startsWith(separator)) {
			remoteFilePath = StringUtils.substring(remoteFilePath, 1);
		}
		
		String ftpDefaultDir = configServiceImpl.getString("ftp_default_dir");
		
		FTPClient ftpClient = getFtpClient();
		ftpClient.setBufferSize(1024*1024*2);
		
		String fileName = FilenameUtils.getName(remoteFilePath);
		String fileDir = FilenameUtils.getFullPathNoEndSeparator(remoteFilePath);
		try {
			boolean changeDirOk = ftpClient.changeWorkingDirectory(ftpDefaultDir);				
				
			if(StringUtils.isNotBlank(fileDir)) {
				changeDirOk = ftpClient.changeWorkingDirectory(fileDir);
				if(!changeDirOk) {
					makeFtpDirs(ftpClient,fileDir);
				}
			}
			ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
			boolean uploadOk = ftpClient.storeFile(fileName, local);
			if(!uploadOk)throw new Exception("上传 " + remoteFilePath + " 失败!");	
			
			return true;
		} catch (Exception e) {
			throw new EbsException(e);
		} finally { 
			IOUtils.closeQuietly(local);
			disconnect(ftpClient); 
		}		
	}
	
	private void downloadFile(String filePath, OutputStream output,String serverNode) throws Exception {
		 	
		ExecTime execTime = new ExecTime();
		logger.info("FTP下载：文件路径 =" + filePath);
		
		while(filePath.startsWith(separator)) {
			filePath = StringUtils.substring(filePath, 1);
		}
		
		FTPClient ftpClient = getFtpClient(serverNode);
		String ftpDefaultDir = configServiceImpl.getString("ftp_default_dir_" + serverNode);
		if(StringUtils.isEmpty(ftpDefaultDir)) 
			ftpDefaultDir = configServiceImpl.getString("ftp_default_dir");
		try {
			boolean changeDirOk = ftpClient.changeWorkingDirectory(ftpDefaultDir);
			boolean downloadOk = false;
			if(changeDirOk) { 
				ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);				 
				downloadOk = ftpClient.retrieveFile(filePath, output);	
			}
			if(!downloadOk)throw new Exception("下载 " + filePath + " 失败!");
		} catch (Exception e) {
			throw new Exception(e);
		} finally {
			logger.info("下载文件：" + filePath + " 耗时："+ execTime);
		}
	}
	
	private void makeFtpDirs(FTPClient ftpClient,String dirName){	
		
		File dir = new File(dirName);
		String currentDirName = dir.getName();
		if(dir.getParent() != null) {				
			makeFtpDir(ftpClient,dir.getParent());
		}
		makeFtpDir(ftpClient,currentDirName);
	}

	private void makeFtpDir(FTPClient ftpClient,String dirName) {
		
		try {
			File dir = new File(dirName);
			if(dir.getParent() != null) {
				makeFtpDir(ftpClient,dir.getParent());
			} 
			
			String currentDirName = dir.getName();
			boolean changeDirOk = ftpClient.changeWorkingDirectory(currentDirName);
			if(!changeDirOk) {
				ftpClient.makeDirectory(currentDirName);					
				changeDirOk = ftpClient.changeWorkingDirectory(currentDirName);
			}
			if(!changeDirOk)throw new Exception("创建目录" + dirName + "失败！");
		} catch (Exception e) {
			throw new EbsException(e);
		} 
	}
	
	
}
